// +build linux

package server

import (
	"net"
	"reflect"
	"sync"
	"syscall"

	"github.com/pkg/errors"
)

type Epoll struct {
	FD    int
	Conns map[int]net.Conn
	Lock  sync.RWMutex
}

func NewEpoll() (*Epoll, error) {
	fd, err := syscall.EpollCreate1(0)
	if err != nil {
		return nil, errors.WithStack(err)
	}
	return &Epoll{
		FD:    fd,
		Lock:  sync.RWMutex{},
		Conns: make(map[int]net.Conn),
	}, nil
}

func (e *Epoll) Add(conn net.Conn) error {
	// Extract file descriptor associated with the connection
	fd := socketFD(conn)
	e.Lock.Lock()
	e.Conns[fd] = conn
	e.Lock.Unlock()
	err := syscall.EpollCtl(e.FD, syscall.EPOLL_CTL_ADD, fd, &syscall.EpollEvent{Events: syscall.EPOLLIN | syscall.EPOLLPRI, Fd: int32(fd)})
	if err != nil {
		e.Lock.Lock()
		delete(e.Conns, fd)
		e.Lock.Unlock()
		return errors.WithStack(err)
	}
	if len(e.Conns)%100 == 0 {
		logger.Infof("total number of connections: %v", len(e.Conns))
	}
	return nil
}

func (e *Epoll) Remove(conn net.Conn) error {
	fd := socketFD(conn)
	err := syscall.EpollCtl(e.FD, syscall.EPOLL_CTL_DEL, fd, nil)
	if err != nil {
		return errors.WithStack(err)
	}
	e.Lock.Lock()
	delete(e.Conns, fd)
	e.Lock.Unlock()
	if len(e.Conns)%100 == 0 {
		logger.Infof("total number of connections: %v", len(e.Conns))
	}
	return nil
}

func (e *Epoll) Wait() ([]syscall.EpollEvent, error) {
	events := make([]syscall.EpollEvent, 100)
	_, err := syscall.EpollWait(e.FD, events, -1)
	if err != nil {
		return nil, errors.WithStack(err)
	}
	return events, nil
}

func (e *Epoll) GetConn(fd int) net.Conn {
	e.Lock.RLock()
	conn := e.Conns[fd]
	e.Lock.RUnlock()
	return conn
}

func socketFD(conn net.Conn) int {
	tcpConn := reflect.Indirect(reflect.ValueOf(conn)).FieldByName("conn")
	fdVal := tcpConn.FieldByName("fd")
	pfdVal := reflect.Indirect(fdVal).FieldByName("pfd")
	return int(pfdVal.FieldByName("Sysfd").Int())
}
